/*
 PureMVC AS3 Demo - Flex Application Skeleton 
 Copyright (c) 2007 Daniele Ugoletti <daniele.ugoletti@puremvc.org>
 Your reuse is governed by the Creative Commons Attribution 3.0 License
*/
package com.larson.creator.model
{
	import com.larson.creator.*;
	import com.larson.creator.model.business.FileLoadDelegate;
	import com.larson.creator.model.vo.*;
	
	import flash.events.Event;
	import flash.filesystem.*;
	import flash.net.FileFilter;
	import flash.xml.*;
	
	import mx.formatters.NumberFormatter;
	import mx.collections.ArrayCollection;
	import mx.rpc.IResponder;
	
	import org.puremvc.as3.interfaces.*;
	import org.puremvc.as3.patterns.proxy.Proxy;
	
	
    /**
     * A proxy for read the config file
     */
    public class FileLoadProxy extends Proxy implements IProxy, IResponder
    {
		public static const NAME:String = "FileLoadProxy";
		
		public var rawData:ArrayCollection;
		public var lineData:ArrayCollection;		
		public var minElevation:Number = 0;
		public var maxElevation:Number = 0;
		private var slopeFormatter:NumberFormatter;		
		private var distanceFormatter:NumberFormatter;		
		private var gpsFormatter:NumberFormatter;	
		
		
		private var crsFile:XML;
		private var dataLen:Number;
		private var dataIdx:Number;
		private var fileType:String;
		private var __trackList:XMLList;
		private var __trackData:Array;
		public var track:ArrayCollection;

		public function FileLoadProxy ( data:Object = null ) 
        {
            super ( NAME, data );
            slopeFormatter = new NumberFormatter();
            distanceFormatter = new NumberFormatter();
            gpsFormatter = new NumberFormatter();
            slopeFormatter.precision = 2;
            slopeFormatter.useNegativeSign = true;
            slopeFormatter.useThousandsSeparator = true;
            distanceFormatter.precision = 3;
            distanceFormatter.useNegativeSign = true;
            distanceFormatter.useThousandsSeparator = true;
            gpsFormatter.precision = 8;
            gpsFormatter.useNegativeSign = true;
            gpsFormatter.useThousandsSeparator = true;            
			
        }
		
		public function load():void
		{
			var fileToOpen:File = new File();
			var txtFilter:FileFilter = new FileFilter("Course File", "*.crs;*.tcx;*.gpx");
			// reset the data 
			this.data = new Object();
			try 
			{
			    fileToOpen.browseForOpen("Open", [txtFilter]);
			    fileToOpen.addEventListener(Event.SELECT, this.fileSelected);
			}
			catch (error:Error)
			{
			    trace("Failed:", error.message)
			}
			

		}

		private function fileSelected(event:Event):void 
		{
		    var filepath:String;
		    filepath = "file://" + event.target.nativePath;
		    
		    fileType = filepath.substr(filepath.length - 4, 4);
		    // Now get the delegate & have it load the selected file
			var delegate : FileLoadDelegate = new FileLoadDelegate(this, filepath);
			// make the delegate do some work
			delegate.load();

		}
		
		// this is called when the delegate receives a result from the service
		public function result( rpcEvent : Object ) : void
		{
			var status:Boolean = false;
			// call the helper class for parse the XML data
			//XmlResource.parse(data, rpcEvent.result);
		    crsFile = new XML(rpcEvent.result);
		    // Get the namespace in the file to determine how to process it...
		    var ns:String = crsFile.namespace().uri;
		    
		    switch( ns ) {
		    	case "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v1":
		    		status = crs(crsFile);
		    	break;
		    	
		    	case "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2":
		    		status = tcx(crsFile);
		    	break;
		    	
		    	case "http://www.topografix.com/GPX/1/1":
		    		status = gpx(crsFile);
		    	break;
		    }
		    if( status == true ) {
				this.sendNotification( ApplicationFacade.LOAD_FILE_COMPLETE );
		    }
		    else {
		    	this.sendNotification( ApplicationFacade.ERROR_LOAD_FILE );
		    }
		}
		
		// this is called when the delegate receives a fault from the service
		public function fault( rpcEvent : Object ) : void 
		{
			// send the failed notification
			this.sendNotification( ApplicationFacade.ERROR_LOAD_FILE );
		}
		
		public function get trackList():XMLList {
			return __trackList;
		}
		
		public function set trackList( newList:XMLList ):void {
			__trackList = newList;
		}
		
		public function get trackData():Array {
			return __trackData;
		}
		
		public function set trackData( newList:Array ):void {
			__trackData = newList;
		}
		
		private function crs( file:XML ):Boolean {
			var status:Boolean = true;
			var ns:Namespace = new Namespace("http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v1");
			default xml namespace = ns;
			__trackList = new XMLList(file.Courses.CourseFolder.Folder.Course.Track.Trackpoint);
			dataLen = __trackList.length();
			dataIdx = 0;
			__trackData = new Array();

			for(dataIdx = 0; dataIdx < dataLen - 1; dataIdx++) {
				var xmlNode:XML = __trackList[dataIdx];
				var rawPoint:CourseDataVO = new CourseDataVO();
				rawPoint.latitude = new Number(xmlNode.Position.LatitudeDegrees);
				rawPoint.longitude = new Number(xmlNode.Position.LongitudeDegrees);
				rawPoint.elevation = xmlNode.AltitudeMeters;
				__trackData.push(rawPoint);
				
			}
			
			return status;
		
		}
		private function tcx( file:XML ):Boolean {
			var status:Boolean = true;
			var trackLen:Number;
			var x:Number;
			var ns:Namespace = new Namespace("http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2");
			default xml namespace = ns;
			var track:XMLList = new XMLList(file.Courses.Course.Track);
			trackLen = track.length();
			
			dataIdx = 0;
			
			__trackData = new Array();

			for(x = 0; x < trackLen; x++ ) {
				__trackList = new XMLList(track[x].Trackpoint);
				dataLen = __trackList.length();
				for(dataIdx = 0; dataIdx < dataLen - 1; dataIdx++) {
					var xmlNode:XML = __trackList[dataIdx];
					if( xmlNode.children().length() > 1 ) {
					var rawPoint:CourseDataVO = new CourseDataVO();
						rawPoint.latitude = new Number(xmlNode.Position.LatitudeDegrees);
						rawPoint.longitude = new Number(xmlNode.Position.LongitudeDegrees);
						rawPoint.elevation = xmlNode.AltitudeMeters;
						__trackData.push(rawPoint);
					}
				}
			}
			return status;
		
		}
		
		private function gpx( file:XML ):Boolean {
			var status:Boolean = true;
			default xml namespace = "http://www.topografix.com/GPX/1/1";
			__trackList = new XMLList(file.trk.trkseg.trkpt);
			dataLen = __trackList.length();
			dataIdx = 0;
			
			__trackData = new Array();

			for(dataIdx = 0; dataIdx < dataLen - 1; dataIdx++) {
				//default xml namespace = "http://www.topografix.com/GPX/1/1";
				var xmlNode:XML = __trackList[dataIdx];
				var rawPoint:CourseDataVO = new CourseDataVO();
				rawPoint.latitude = new Number(__trackList[dataIdx].@lat);
				rawPoint.longitude = new Number(__trackList[dataIdx].@lon);
				rawPoint.elevation = 0;
				__trackData.push(rawPoint);
				
			}
			return status;
		
		}

		private function elevationCheck( el:Number ):void {
			if( el < minElevation ) {
				minElevation = el;
			}
			if( el > maxElevation ) {
				maxElevation = el;
			}
		}
		
		public function createRawData( trackList:Array, cVO:ControlsVO ):void {
			
			var idx:Number = 0;
		
			var len:Number = trackList.length;
			var segText:String;
			var elevationChg:Number;
			var totalDistance:Number = 0;
			var lineArray:Array = new Array();
			
			rawData = new ArrayCollection();
			minElevation = 1000000;
			maxElevation = 0;
			
			for(idx = 0; idx < len - 1; idx++) {
				var rawPoint1:CourseDataVO = trackList[idx];
				var rawPoint2:CourseDataVO = trackList[idx + 1];
				var minDist:Number = new Number(cVO.minDist);
				
				rawPoint1.latitude = new Number(gpsFormatter.format(rawPoint1.latitude));
				rawPoint1.longitude = new Number(gpsFormatter.format(rawPoint1.longitude));
				elevationCheck(rawPoint1.elevation);
				
				rawPoint2.latitude = new Number(gpsFormatter.format(rawPoint2.latitude));
				rawPoint2.longitude = new Number(gpsFormatter.format(rawPoint2.longitude));
				elevationCheck(rawPoint2.elevation);
								
				var ptArray:Array = new Array([rawPoint1,rawPoint2]);
				var distance:Number = new Number(distanceFormatter.format(distHaversine(rawPoint1.latitude, rawPoint1.longitude, rawPoint2.latitude, rawPoint2.longitude)));
				totalDistance += distance;
				if( idx == 0 ) {
					rawData.addItem(rawPoint1);
					var ld:LineDataVO = new LineDataVO(); 
					ld.x = 0;
					ld.y = rawPoint1.elevation;
					ld.slope = 0;
					lineArray.push(ld);
				}
				if(distance > minDist) {
					rawPoint2.length = distance;
					rawPoint2.elChange = rawPoint2.elevation - rawPoint1.elevation;
					rawPoint2.slope = new Number(slopeFormatter.format(getElevationChange(rawPoint2.elChange, distance)));
					rawData.addItem(rawPoint2);
					var lData:LineDataVO = new LineDataVO(); 
					lData.x = totalDistance;
					lData.y = rawPoint2.elevation;
					lData.slope = rawPoint2.slope;
					lineArray.push(lData);
				}
				else {
					trace("Short Seg");

				}
			}
			// Smooth the line data if indicated.
/*			if( cVO.elSmoothing == true ) {
				var lineGeneralization:LineGeneralization = new LineGeneralization();
				for( idx = 0; idx < new Number(cVO.smFactor); idx++ ) {
					lineArray = lineGeneralization.smoothMcMaster(lineArray, 5);
				}
			}
			for( idx = 0; idx < lineArray.length; idx++ ) {
				lineArray[idx].y = lineArray[idx].y - minElevation;
			}
			
			correctedData = new ArrayCollection();
			for( idx = 0; idx < (lineData.length -1); idx++ ) {
				var segDistance:Number = lineData[idx+1].x - lineData[idx].x;
				var segSlope:Number = getElevationChange(lineArray[idx+1].y - lineArray[idx].y, segDistance);
				lineArray[idx+1].slope = segSlope;
				var newSeg:LineDataVO = new LineDataVO();
				
				newSeg.x = segDistance;
				newSeg.y = segSlope;
				correctedData.addItem(newSeg);

			}
*/			
			lineData = new ArrayCollection(lineArray);
			this.sendNotification( ApplicationFacade.DATA_READY );

		}

		private function distHaversine (lat1:Number, lon1:Number, lat2:Number, lon2:Number):Number {
		  var R:Number = 6371; // earth's mean radius in km
		  var dLat:Number = toRad(lat2-lat1);
		  var dLon:Number = toRad(lon2-lon1);
		
		  var tlat1:Number = toRad(lat1);
		  var tlat2:Number = toRad(lat2);  
		
		
		  var a:Number = (Math.sin(dLat/2) * Math.sin(dLat/2)) +  (Math.cos(tlat1) * Math.cos(tlat2) * (Math.sin(dLon/2) * Math.sin(dLon/2)));
		  var c:Number = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
		  var d:Number = R * c;
		
		  return d;
		}
		
		private function toRad(str:Number):Number {  // convert degrees to radians
		  return str * (Math.PI / 180);
		
		}
		
		private function getElevationChange(elChange:Number, dist:Number):Number {

			var meterDistance:Number = dist * 1000;
			var slope:Number;
			if( meterDistance > 0 ) {
				slope = (elChange/meterDistance) * 100;
			} else {
				slope = 0;
			}
			return slope;
		}


	}
}